<?php namespace xlx;
use xlx\lib\ArrayQueue;
use xlx\lib\CircleQueue;


class PrActor
{

    public $mailbox = null;
    public $receive = null;
    

    public $coid = null;//对外标记(ip:hash)
    public $gen = null;//终止标记 
    public static $local_actors=[];//本地标识（hash)
    public static $local_addr=null;
    
    public function __construct( )
    {
        if (empty(\xlx\PrService::$reg_conn)) {
            \xlx\PrService::connectServer(true);
        }
        //自动启动
        $this->coid = $coid = self::$local_addr.":".\spl_object_hash($this);
        $this->mailbox = new ArrayQueue();
        $this->receive = new CircleQueue(); 
    }
    public function __destruct(){
        $bug = true; 
        // if(isset(\xlx\PrService::$client_monitor[$this->coid])){
        //     $arr = \xlx\PrService::$client_monitor[$this->coid];
        //     unset(\xlx\PrService::$client_monitor[$this->coid]);
        //     \xlx\PrService::$reg_conn->send(['exit',$this->coid]);
        // }

        //\xlx\PrService::sendTo($this->coid,['EXIT']); 
    }
    public static function self()
    {
        $stack_list = debug_backtrace();
        for ($i=0; $i < count($stack_list); $i++) {
            $stack = $stack_list[$i];
            if (isset($stack['object']) && $stack['object'] instanceof self) {
                $self = $stack['object'];
            }
            if (isset($self) && empty($self->gen) && isset($stack['class']) && $stack['class']=='Generator') {
                $self->gen = $stack['object'];
            }
            if (isset($self) && isset($self->gen)) {
                return $self->coid;
            }
        }
        throw new Exception("Error Processing Request", 1);
    }


    public function notify($k, $defer, $i, $msg){
        try {
            if (null!==($data = \call_user_func($defer->fn, $msg))) {
                $this->mailbox->splice($i);
                $this->receive->splice($k);
                $defer->resolve($data);
                return true;
            }
        } catch (\Throwable $e) {
            $defer->reject($e); 
            return true;
        } 
    }
    ///////////////// actor 同步函数 ///////////////////

    public static function send($coid, $msg)
    {
        if (empty(self::$local_actors[$coid])) {
            preg_match('/([\d\.]+:\d+):(\w+)/',$coid,$arr);
            if(isset($arr)){
                \xlx\PrService::clientCase("udp://{$arr[1]}",['send',$coid,$msg]);
            }else{
                throw new Exception("Error Processing Request", 1); 
            }
        }else{ 
            $actor = self::$local_actors[$coid];
            $i = $actor->mailbox->push($msg);
            foreach ($actor->receive as $k => $defer) {
                if ($actor->notify($k, $defer, $i, $msg )) {
                    break;
                }
            }
        }
    }

    public static function exit($coid, $reason_from )
    {  
        //transform process
        if (empty(self::$local_actors[$coid])) { 
            preg_match('/([\d\.]+:\d+):(\w+)/',$coid,$arr);
            if(isset($arr) && $arr[1] != self::$local_addr){
                \xlx\PrService::clientCase($arr[1],['exit',$coid,$reason_from]);
                
            }
            return; //throw new Exception("Error Processing Request", 1);  
        }
        // 
        if(isset(\xlx\PrService::$client_monitor[$coid])){ 
            unset(\xlx\PrService::$client_monitor[$coid]);
            \xlx\PrService::$reg_conn->send(['exiting',$coid]);
        } 


        //transform message
        if (isset(\xlx\PrService::$client_exit[$coid])
        && \xlx\PrService::$client_exit[$coid] == true ) { 
            self::$local_actors[$coid]->send($coid,['EXIT', $reason_from ]);
            return ;
        }

        $gen = self::$local_actors[$coid]->gen;
        if (isset($gen->promise)) {
            //非 Promise/A 标准代码  这里用defered
            $ref = new \ReflectionClass(\React\Promise\Promise::class); 
            $method = $ref->getMethod('reject');
            $method->setAccessible(true); 
            $attr = $ref->getProperty('result'); 
            $attr->setAccessible(true); 
            if( $attr->getValue($gen->promise) ){
                throw new \Exception("Error \Workerman\Events\Select Not Support `nextTick()`", 1);
                // \Workerman\Events\Select 不支持推迟到下一帧执行（0秒执行）
            } 
            $method->invokeArgs($gen->promise, [
                new \Exception("Error Process Abort", 1)
            ]); 
            // $method->invokeArgs($gen->promise, [
            //     \React\Promise\reject(new \Exception("Error Process Abort", 1))
            // ]);
            //unset($gen->promise);
        } else {
            $gen->throw(new \Exception("Error Process Abort", 1));
        }
    } 
    
    public static function trapexit($bool){
        $from = self::self();
        \xlx\PrService::$client_exit[$from]=$bool;
    }
    
    //////////////// actor 异步函数 ///////////////////// 

    public static function receive($fn = null, $delay = -1, $after = null)
    {
        if (empty($fn)) {
            $fn = function ($v) {
                return $v;
            };
        }
        $defer = new \Hprose\Deferred;
        $defer->fn=$fn;
        $actor = self::$local_actors[static::self()];
        $k = $actor->receive->push($defer);
        foreach ($actor->mailbox as $i => $msg) {
            if ($actor->notify( $k, $defer, $i, $msg )) {
                break;
            }
        }
        return $defer->promise;//();
    }
    public static function spawn_opt($fn,$args=[],$opt=[])
    { 
        return \Hprose\Future\co(function()use($fn,$args,$opt){ 
            if (isset($opt['ip']) && $opt['ip'] !== self::$local_addr) {
                if($fn instanceof \Closure){   
                    $fn = $fn->bindTo(null);
                    $fn = (serialize(new \Opis\Closure\SerializableClosure($fn)) );
                } 
                return \xlx\PrService::clientCall($opt['ip'],['spawn',$fn,$args,$opt]);
            }else{
                if (is_string($fn) && substr($fn, 0, 38)
                    =='C:32:"Opis\Closure\SerializableClosure') {
                    $fn = unserialize($fn)->getClosure();
                }
                switch(true){
                    case $fn instanceof \Closure:
                        $actor = new \xlx\PrActor; 
                        $fn = $fn->bindTo($actor);
                        break;
                    case \is_array($fn):
                        list($class,$method) = $fn;
                        $actor = new $class;
                        $fn = [$actor,$method];
                        break;
                    default:
                        throw new Exception("Error Processing Request", 1);  
                } 
                $coid = $actor->coid;
                static::$local_actors[$coid] = $actor; 
                if(isset($opt['link']) && 'err'== yield self::link($coid)){
                    throw new \Exception("Error Processing Request", 1); 
                }
                \Hprose\Future\co(function()use($fn,$args,$coid){
                    try{ 
                        yield $fn(...$args);
                    }catch(\Throwable $e){
                        echo $e;
                    } 
                    unset(static::$local_actors[$coid]->gen);
                    unset(static::$local_actors[$coid]);  
                    if(isset(\xlx\PrService::$client_monitor[$coid])){ 
                        unset(\xlx\PrService::$client_monitor[$coid]);
                        \xlx\PrService::$reg_conn->send(['exiting',$coid]);
                    }  
                });
                // \xlx\co($fn,...$args)->then(null,function($e){ 
                // })->always(function()use($coid) {//自动释放   
                // });
                return $coid;
            }
        });
    }
    public static function spawn($fn,$args=[]){ 
        $table = \xlx\PrService::$client_data['iptable'];
        $opt['ip'] = $table[array_rand($table)]??self::$local_addr; 
        return self::spawn_opt($fn,$args,$opt);
    }
    public static function spawn_link($fn,$args=[]){ 
        $table = \xlx\PrService::$client_data['iptable'];
        $opt['ip'] = $table[array_rand($table)]??self::$local_addr;
        $opt['link'] = true;// self::self();
        return self::spawn_opt($fn,$args,$opt);
    }
 

    public static function link($coid){  
        return \xlx\co(function()use($coid){
            $self = self::self();
            preg_match('/([\d\.]+:\d+):(\w+)/',$coid,$arr);
            $result = yield \xlx\PrService::clientCall($arr[1],['monitor',$coid,$self]);
            if($result == 'ok'){
                \xlx\PrService::$client_monitor[$self][$coid]=true;
                \xlx\PrService::serverCase(['monitor',$self,$coid]); 
            }
            return $result;
        });
    }
    public static function monitor($coid){
        return \xlx\co(function()use($coid){
            $self = self::self();
            preg_match('/([\d\.]+:\d+):(\w+)/',$coid,$arr);
            $result = yield \xlx\PrService::clientCall($arr[1],['monitor',$coid,$self]); 
            return $result;
        }); 
    }


    public static function unlink($coid){ 
        return \xlx\co(function()use($coid){
            $self = self::self();
            preg_match('/([\d\.]+:\d+):(\w+)/',$coid,$arr);
            $result = yield \xlx\PrService::clientCall($arr[1],['demonitor',$coid,$self]);
            if($result == 'ok'){
                unset(\xlx\PrService::$client_monitor[$self][$coid]);
                \xlx\PrService::serverCase(['demonitor',$self,$coid]); 
            }
            return $result;
        });
    }


}
//
//断开时候通知我
//广播？？